home *** CD-ROM | disk | FTP | other *** search
- /*
- * timerMC.c --
- *
- * This file contains routines that manipulate the MC 146818 real-time
- * clock.
- *
- * For a detailed explanation of the chip, see the "PMAX Desktop
- * Workstation Functional Specification, Revision 1.1" pages 62-66.
- *
- * Copyright (C) 1989 Digital Equipment Corporation.
- * Permission to use, copy, modify, and distribute this software and
- * its documentation for any purpose and without fee is hereby granted,
- * provided that the above copyright notice appears in all copies.
- * Digital Equipment Corporation makes no representations about the
- * suitability of this software for any purpose. It is provided "as is"
- * without express or implied warranty.
- */
-
- /*
- * This chip has a real-time clock (RTC) and an interval timer. The real-time
- * clock is basically useless because it only has a resolution of seconds.
- * The machine-independent part assumes that we have a free-running counter
- * which has a resolution of milliseconds. What we end up doing is
- * faking the free running counter. The RTC generates an interrupt every
- * second which we use to increment the seconds counter and clear the
- * microseconds counter. The interval timer is used to increment the
- * microseconds counter. We have a problem when interrupts are turned off
- * since the counter doesn't get incremented. This is fixed by comparing
- * the difference in the hardware clock since the last interrupt against
- * the difference in the software clock. The hardware clock is supposed
- * to go invalid 244 microseconds after the interrupt. It isn't clear
- * when the update-in-progress flag is set. My reading of the
- * manual leads me to believe that it is set at the same time as the
- * interrupt. In this were true we could never read the hardware clock
- * in the interrupt handler. Either this isn't true, or we are usually
- * delayed enough getting to the handler so that the clock is valid
- * again because the update-in-progress bit is never set (I ran a few
- * tests).
- */
-
- #ifndef lint
- static char rcsid[] = "$Header: /cdrom/src/kernel/Cvsroot/kernel/timer/ds3100.md/timerMC.c,v 9.5 90/10/19 15:58:06 rab Exp $ SPRITE (Berkeley)";
- #endif
-
- #include <sprite.h>
- #include <sys.h>
- #include <timerInt.h>
- #include <timerTick.h>
- #include <spriteTime.h>
- #include <mach.h>
- #include <machMon.h>
- #include <prof.h>
- #include <timer.h>
- #include <machAddrs.h>
- #include <assert.h>
-
- /*
- * Control register A.
- */
- #define REGA_UIP 0x80
- #define REGA_TIME_DIV 0x70
- #define REGA_RATE_SELECT 0x0F
-
- /*
- * Time base to use in the REGA_TIME_DIV field.
- */
- #define REGA_TIME_BASE 0x20
-
- /*
- * Set the interval at 7.8125 ms. RATE_US is the number of microseconds
- * to add to the counter at each interrupt. WHEN_TO_ADD_ONE says after
- * how many intervals should one extra microsecond be added in. This
- * is necessary because the interval is actually 7812.5 microseconds.
- */
- #define SELECTED_RATE 0x9
- #define RATE_US 7812
- #define WHEN_TO_ADD_ONE 0x1
-
- /*
- * Set up the interval structures. These are passed to Timer_CallBack
- * and are used to compute the interval between callbacks. Since we have
- * an interval of 7812.5 on the stupid ds3100 we have two structures and
- * alternate passing them to Timer_CallBack.
- * The interval is represented both as a Time, and as an integer. It is
- * too expensive to convert between them later so set them both up here.
- */
-
-
-
- static Time lowTime = {0, 7812};
- static int lowInterval = 7812;
- static Time highTime = {0, 7813};
- static int highInterval = 7813;
-
- /*
- * Control register B.
- */
- #define REGB_SET_TIME 0x80
- #define REGB_PER_INT_ENA 0x40
- #define REGB_UPDATE_INT_ENA 0x10
- #define REGB_DATA_MODE 0x04
- #define REGB_HOURS_FORMAT 0x02
-
- /*
- * Control register C.
- */
- #define REGC_INT_PENDING 0x80
- #define REGC_PER_INT_PENDING 0x40
- #define REGC_UPDATE_INT_PENDING 0x10
-
- /*
- * Control register D.
- */
-
- #define REGD_VALID_TIME 0x80
- /*
- * Pointers to registers.
- */
- volatile unsigned char *secPtr = (unsigned char *)(MACH_CLOCK_ADDR + 0x00);
- volatile unsigned char *minPtr = (unsigned char *)(MACH_CLOCK_ADDR + 0x08);
- volatile unsigned char *hourPtr = (unsigned char *)(MACH_CLOCK_ADDR + 0x10);
- volatile unsigned char *dayPtr = (unsigned char *)(MACH_CLOCK_ADDR + 0x1C);
- volatile unsigned char *monPtr = (unsigned char *)(MACH_CLOCK_ADDR + 0x20);
- volatile unsigned char *yearPtr = (unsigned char *)(MACH_CLOCK_ADDR + 0x24);
- volatile unsigned char *regAPtr = (unsigned char *)(MACH_CLOCK_ADDR + 0x28);
- volatile unsigned char *regBPtr = (unsigned char *)(MACH_CLOCK_ADDR + 0x2C);
- volatile unsigned char *regCPtr = (unsigned char *)(MACH_CLOCK_ADDR + 0x30);
- volatile unsigned char *regDPtr = (unsigned char *)(MACH_CLOCK_ADDR + 0x34);
-
- /*
- * We store a couple of things in the non-volatile ram.
- */
-
- #define NVR_ADDR (MACH_CLOCK_ADDR + 0x38)
-
- volatile unsigned char *localOffsetNVMPtr = (unsigned char *) (NVR_ADDR + 0x00);
- volatile unsigned char *DSTAllowNVMPtr = (unsigned char *) (NVR_ADDR + 0x04);
-
- #define ONE_MILLION 1000000
-
- /*
- * The "free running counter"
- */
- Timer_Ticks counter;
-
- Boolean callbackIntrsWanted = FALSE;
- Boolean profileIntrsWanted = FALSE;
-
-
- /*
- * The RTC registers can only be accessed one byte at a time. This routine
- * is used to write words into the non-volatile storage.
- */
-
- #define BYTECOPY(a,b,num) { \
- int i; \
- for (i = 0; i < (num); i++) { \
- ((char *) (b))[i] = ((char *) (a))[i]; \
- } \
- }
-
- /*
- * Used for debugging. Counts number of times free-running counter was
- * corrected.
- */
- int timerCorrectedClock;
-
-
- /*
- *----------------------------------------------------------------------
- *
- * Timer_TimerInit --
- *
- * Initialize the periodic timer.
- *
- * N.B. This routine must be called before Timer_TimerStart.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The timer is initialized.
- *
- *----------------------------------------------------------------------
- */
- /*ARGSUSED*/
- void
- Timer_TimerInit(timer)
- unsigned short timer;
- {
- *regAPtr = REGA_TIME_BASE | SELECTED_RATE;
- *regBPtr = REGB_DATA_MODE | REGB_HOURS_FORMAT;
- }
-
-
- /*
- *----------------------------------------------------------------------
- *
- * Timer_TimerStart --
- *
- * Start the timer ticking.
- * ands starts the timer.
- *
- * N.B. The timer must have been initialized with Timer_TimerInit
- * before this routine is called.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The timer starts ticking.
- *
- *----------------------------------------------------------------------
- */
- void
- Timer_TimerStart(timer)
- register unsigned short timer;
- {
- #ifndef lint
- unsigned char dummy;
- #endif
-
- *regBPtr |= REGB_PER_INT_ENA;
- #ifndef lint
- dummy = *regCPtr;
- #endif
-
- Mach_MonPrintf("Starting timer interrupts.\n");
- if (timer == TIMER_CALLBACK_TIMER) {
- callbackIntrsWanted = TRUE;
- } else if (timer == TIMER_PROFILE_TIMER) {
- profileIntrsWanted = TRUE;
- } else {
- panic("Timer_TimerStart: unknown timer %d\n", timer);
- }
- }
-
-
- /*
- *----------------------------------------------------------------------
- *
- * Timer_TimerInactivate --
- *
- * Stops the specified timer such that it will cease counting and
- * also resests the mode register to 0. If the timer has already
- * stopped and has set its output line high, clear the output so it
- * won't cause an interrupt (because we don't care that it has
- * expired).
- *
- * Results:
- * None.
- *
- * Side effects:
- * The timer is stopped.
- *
- *----------------------------------------------------------------------
- */
- void
- Timer_TimerInactivate(timer)
- register unsigned short timer;
- {
- if (timer == TIMER_CALLBACK_TIMER) {
- callbackIntrsWanted = FALSE;
- } else if (timer == TIMER_PROFILE_TIMER) {
- profileIntrsWanted = FALSE;
- } else {
- panic("Timer_TimerInactivate: unknown timer %d\n", timer);
- }
-
- /*
- * If neither type of timer interrupt is wanted, then disable
- * timer interrupts.
- */
- if (!callbackIntrsWanted && !profileIntrsWanted) {
- *regBPtr &= ~REGB_PER_INT_ENA;
- }
- }
-
-
- /*
- *----------------------------------------------------------------------
- *
- * Timer_TimerServiceInterrupt --
- *
- * This routine is called at every timer interrupt.
- * It calls the timer callback queue handling if the callback timer
- * expired and calls the profiling interrupt handling if the
- * profile callback timer expired.
- *
- * Results:
- * None.
- *
- * Side Effects:
- * Routines on the timer queue may cause side effects. Profile
- * collect may take place.
- *
- *
- *----------------------------------------------------------------------
- */
- void
- Timer_TimerServiceInterrupt(statusReg, causeReg, pc)
- unsigned int statusReg;
- unsigned int causeReg;
- Address pc;
- {
- static unsigned addOne = 0;
- unsigned char timerStatus;
- Time *timePtr;
- unsigned int interval;
- Time_Parts currentTODParts;
- static int previousSoftSeconds;
- static int previousHardSeconds;
- int softSeconds;
- int hardSeconds;
- int diff;
- static Boolean initialized = FALSE;
-
- static int eventDebug[1000];
- static int dbgCtr = 0;
- #define INC(a) { (a) = ((a) + 1) % 1000; }
-
- timerStatus = *regCPtr;
- eventDebug[dbgCtr] = 0;
- if (timerStatus & REGC_PER_INT_PENDING) {
- /*
- * Increment the counter.
- */
- counter.microseconds += RATE_US;
- if ((addOne & WHEN_TO_ADD_ONE) == WHEN_TO_ADD_ONE) {
- counter.microseconds++;
- timePtr = &highTime;
- interval = highInterval;
- } else {
- timePtr = &lowTime;
- interval = lowInterval;
- }
- addOne++;
- if (counter.microseconds >= ONE_MILLION) {
- /*
- * We wrapped around.
- */
- counter.microseconds = ONE_MILLION - 1;
- }
- }
- if ((timerStatus & REGC_UPDATE_INT_PENDING)) {
- /*
- * RTC interrupt.
- */
- counter.seconds++;
- counter.microseconds = 0;
- eventDebug[dbgCtr] |= 0x1;
- if ((*regAPtr & REGA_UIP) == 0) {
- currentTODParts.seconds = *secPtr;
- currentTODParts.minutes = *minPtr;
- currentTODParts.hours = *hourPtr;
- currentTODParts.dayOfMonth = *dayPtr;
- currentTODParts.dayOfYear = -1;
- currentTODParts.month = *monPtr-1;
- currentTODParts.year = *yearPtr;
- (void) Time_FromParts(¤tTODParts, FALSE, &hardSeconds);
- softSeconds = counter.seconds;
- diff = hardSeconds - previousHardSeconds;
- eventDebug[dbgCtr] |= 0x4;
- if (softSeconds - previousSoftSeconds != diff && initialized) {
- /*
- * Note that the software counter cannot be ahead of
- * the hardware counter. We are only looking at the
- * seconds, and the seconds are only incremented
- * during an interrupt. We only get one interrupt
- * a second so we may be behind but not ahead.
- */
- if (softSeconds - previousSoftSeconds > diff) {
- panic("Software time is ahead of hardware!\n");
- }
- counter.seconds = previousSoftSeconds + diff;
- softSeconds = counter.seconds;
- timerCorrectedClock++;
- eventDebug[dbgCtr] |= 0x8;
- }
- previousHardSeconds = hardSeconds;
- previousSoftSeconds = softSeconds;
- initialized = TRUE;
- }
- }
- if (timerStatus & REGC_PER_INT_PENDING) {
- if (mach_KernelMode) {
- assert((statusReg & MACH_SR_KU_PREV) == 0);
- /*
- * Check for kernel profiling. We'll sample the PC here.
- */
- if (profileIntrsWanted) {
- TIMER_PROFILE_ROUTINE(pc);
- }
- } else {
- assert((statusReg & MACH_SR_KU_PREV) != 0);
- Proc_GetCurrentProc()->Prof_PC = (int) pc;
- }
- TIMER_CALLBACK_ROUTINE(interval, *timePtr);
- }
- INC(dbgCtr);
- }
-
-
- /*
- *----------------------------------------------------------------------
- *
- * Timer_CounterInit --
- *
- * Initialize free-running counter.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The specified counters begin to count.
- *
- *----------------------------------------------------------------------
- */
- void
- Timer_CounterInit()
- {
- counter.seconds = 0;
- counter.microseconds = 0;
- }
-
-
- /*
- *----------------------------------------------------------------------
- *
- * Timer_GetCurrentTicks --
- *
- * Return microseconds since boot.
- *
- * Results:
- * The system up-time in ticks.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
- void
- Timer_GetCurrentTicks(ticksPtr)
- Timer_Ticks *ticksPtr; /* Buffer to place current time. */
- {
- DISABLE_INTR();
-
- *ticksPtr = counter;
-
- ENABLE_INTR();
- }
-
-
- /*
- *----------------------------------------------------------------------
- *
- * Timer_GetInfo --
- *
- * Dummy routine to dump timer state.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
- void
- Timer_TimerGetInfo()
- {
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * TimerHardwareUniversalTimeInit --
- *
- * Checks the battery backed up clock. If the contents are still
- * valid then we return them.
- *
- * IMPORTANT: right now it looks like the ds3100 RTC is reset
- * by the prom during the boot.
- *
- * Results:
- * None.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
- void
- TimerHardwareUniversalTimeInit(timePtr, localOffsetPtr, DSTPtr)
- Time *timePtr; /* Buffer to hold universal time. */
- int *localOffsetPtr; /* Buffer to hold local offset. */
- Boolean *DSTPtr; /* Buffer to hold DST allowed flag. */
- {
- Time_Parts timeParts;
- int seconds;
- ReturnStatus status;
-
- while ((*regAPtr & REGA_UIP) == 1) {
- }
- timeParts.seconds = *secPtr;
- timeParts.minutes = *minPtr;
- timeParts.hours = *hourPtr;
- timeParts.dayOfMonth = *dayPtr;
- timeParts.dayOfYear = -1;
- timeParts.month = *monPtr-1;
- timeParts.year = *yearPtr;
- status = Time_FromParts(&timeParts, FALSE, &seconds);
- if (status != SUCCESS) {
- Mach_MonPrintf("Time stored in RTC is bogus.\n");
- return;
- }
- if (*regDPtr & REGD_VALID_TIME == 0) {
- Mach_MonPrintf(
- "Warning: battery backed up TOD clock is invalid.\n");
- return;
- }
- bzero((Address) timePtr, sizeof(Time));
- timePtr->seconds = seconds;
- BYTECOPY(localOffsetNVMPtr, localOffsetPtr, 4);
- BYTECOPY(DSTAllowNVMPtr, DSTPtr, 4);
- }
-
- /*
- *----------------------------------------------------------------------
- *
- * TimerSetHardwareUniversalTime --
- *
- * Sets the hardware RTC clock. Has to be called with interrupts
- * off.
- *
- * IMPORTANT: The RTC is used to keep the free-running counter
- * up-to-date if we miss a few interrupts. DO NOT RESET THE RTC
- * WHILE THE KERNEL IS RUNNING!!! If you do system time will
- * not be linear and bad things will happen. Only call this routine
- * during startup and shutdown. As things now stand there is no
- * reason to call this routine at all since the RTC does not
- * appear to persist across reboots.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The hardware RTC is set.
- *
- *----------------------------------------------------------------------
- */
-
- void
- TimerSetHardwareUniversalTime(timePtr, localOffset, DST)
- Time *timePtr; /* universal time. */
- int localOffset; /* local offset. */
- Boolean DST; /* DST allowed flag. */
- {
- Time_Parts timeParts;
-
- Time_ToParts(timePtr->seconds, FALSE, &timeParts);
- *regBPtr |= REGB_SET_TIME;
- *secPtr = (unsigned char) timeParts.seconds;
- *minPtr = (unsigned char) timeParts.minutes;
- *hourPtr = (unsigned char) timeParts.hours;
- *dayPtr = (unsigned char) timeParts.dayOfMonth;
- *monPtr = (unsigned char) timeParts.month+1;
- *yearPtr = (unsigned char) timeParts.year;
- BYTECOPY(&localOffset,localOffsetNVMPtr, 4);
- BYTECOPY(&DST, DSTAllowNVMPtr, 4);
- *regBPtr &= ~REGB_SET_TIME;
- }
-